import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:args/args.dart';
import 'package:crypto/crypto.dart';
import 'package:hotcode/model/config.dart';
import 'package:hotcode/model/store.dart';
import 'package:hotcode/util/date_util.dart';
import 'package:jaguar/jaguar.dart';
import 'package:path/path.dart';

///静态HTTP 服务器
void main(List<String> arguments) async {
  final ArgParser argParser = ArgParser();
  try {
    argParser
      ..addOption('port', help: '服务端口，例如：8080')
      ..addOption('path', help: '服务路径，例如：/public/*')
      ..addOption('directory', help: '服务目录，例如：./public/')
      ..addOption('token', help: '上传令牌，例如：abc123')
      ..addOption('serverUrl', help: '服务链接，例如：http://localhost:8080/public')
      ..addOption('indexFile', help: '一键安装路径，例如：./public/ios/index.html')
      ..addOption('config', help: '配置文件，例如：./config.json')
      ..addFlag('help', help: '显示帮助', negatable: false);
    final ArgResults argResults = argParser.parse(arguments);
    if (argResults['help'] ?? false) {
      _printUsage("使用方法：", argParser.usage);
      return;
    }
    final Map<String, dynamic> configFromFile = argResults['config'] != null
        ? jsonDecode(File(argResults['config']).readAsStringSync())
        : {};
    final Config config = Config(
      port: argResults['port'] != null
          ? int.parse(argResults['port'])
          : configFromFile['port'],
      path: argResults['path'] ?? configFromFile['path'],
      indexFile: argResults['indexFile'] ?? configFromFile['indexFile'],
      token: argResults['token'] ?? configFromFile['token'],
      directory: argResults['directory'] ?? configFromFile['directory'],
      serverUrl: argResults['serverUrl'] ?? configFromFile['serverUrl'],
      storeDirectory: "store",
      packageDirectory: "package_store",
    );
    if (config.port == null || config.port < 1 || config.port > 65535) {
      _printUsage("非法服务端口，正确范围：1~65535", argParser.usage);
      return;
    }
    if (config.path == null) {
      _printUsage("请配置正确服务路径，例如：/public/*", argParser.usage);
      return;
    }
    if (config.directory == null) {
      _printUsage("请配置正确服务目录，例如：public", argParser.usage);
      return;
    }
    if (config.serverUrl == null) {
      _printUsage(
          "请配置正确服务URL，例如：http://localhost:8080/public", argParser.usage);
      return;
    }

    print("服务端口: ${config.port}");
    print("服务路径: ${config.path}");
    print("服务目录: ${config.directory}");
    print("上传令牌: ${config.token}");
    print("服务URL: ${config.serverUrl}");
    print("一键安装路径: ${config.indexFile}");
    final File storeFile = File("./${config.storeDirectory}/store.json");
    if (!storeFile.existsSync()) {
      //创建目录
      storeFile.createSync(recursive: true);
      //写空
      storeFile.writeAsStringSync("[]");
    }
    final String storePath =
        File("./${config.storeDirectory}/store.json").readAsStringSync() ??
            "[]";
    final List<dynamic> storeJsonList = jsonDecode(storePath);
    final List<Store> storeList =
        storeJsonList.map((e) => Store.fromJson(e)).toList();
    await Jaguar(port: config.port)
      //上传
      ..post('/package', (context) async {
        final Map<String, FormField> formData = await context.bodyAsFormData();
        return await _uploadPackage(formData, config, storeList);
      })
      //获取单个
      ..get("/package/:bundleIdentifier/:bundleVersion/:buildNumber",
          (context) {
        final String bundleIdentifier =
            context.pathParams.get('bundleIdentifier');
        final String bundleVersion = context.pathParams.get('bundleVersion');
        final String buildNumber = context.pathParams.get('buildNumber');
        return _prettyEncode(storeList
                .firstWhere((element) =>
                    element.bundleIdentifier == bundleIdentifier &&
                    element.bundleVersion == bundleVersion &&
                    element.buildNumber == buildNumber)
                ?.toJson() ??
            {});
      })
      //获取所有
      ..get("/package", (context) {
        return File("./${config.storeDirectory}/store.json").readAsStringSync();
      })
      ..staticFiles(config.path, config.directory)
      ..log.onRecord.listen(print)
      ..serve(logRequests: true);
    print("服务器启动成功");
  } catch (e, s) {
    print(e);
    print(s);
    _printUsage("使用方法：", argParser.usage);
  }
}

///打印帮助信息
_printUsage(String title, String usage) {
  print(title);
  print(usage);
}

///计算文件MD5
Future<String> fileMd5(File localFile) async {
  final Uint8List bytes = await localFile.readAsBytes();
  final Digest digest = md5.convert(bytes);
  final String hash = "$digest";
  return hash;
}

///处理包上传
Future<String> _uploadPackage(final Map<String, FormField> formData,
    Config config, List<Store> storeList) async {
  try {
    final start = DateTime.now().millisecondsSinceEpoch;
    final String bundleIdentifier =
        _formatPath(formData['bundleIdentifier'].value);
    final String bundleVersion = _formatPath(formData['bundleVersion'].value);
    final String buildNumber = _formatPath(formData['buildNumber'].value);
    final String title = formData['title'].value;
    final String subtitle = formData['subtitle'].value;
    final String token = formData['token'].value;
    if (token != config.token) {
      return jsonEncode({"code": 401, "msg": "非法访问"});
    }
    final String uid = _buildUid(bundleIdentifier, bundleVersion, buildNumber);
    final BinaryFileFormField displayImage = formData['displayImage'];
    final BinaryFileFormField packageFile = formData['packageFile'];
    final Directory packageDirectoryFile =
        Directory(join(config.directory, "${config.packageDirectory}"));
    if (!packageDirectoryFile.existsSync()) {
      packageDirectoryFile.createSync(recursive: true);
    }
    final String packageFilePath = join(packageDirectoryFile.path, "$uid.ipa");
    await packageFile.writeTo(packageFilePath);
    await displayImage.writeTo(join(packageDirectoryFile.path, "$uid.png"));
    final File plist = File(join(packageDirectoryFile.path, "$uid.plist"));
    await plist.writeAsString(_buildPlist(
        config, bundleIdentifier, bundleVersion, buildNumber, title, subtitle));

    storeList.removeWhere((element) =>
        element.bundleIdentifier == bundleIdentifier &&
        element.bundleVersion == bundleVersion &&
        element.buildNumber == buildNumber);
    final Store store = Store.fromJson({
      "bundleIdentifier": bundleIdentifier,
      "bundleVersion": bundleVersion,
      "buildNumber": buildNumber,
      "title": title,
      "subtitle": subtitle,
    });
    //为了加快执行速度，暂时不计算md5
    store.md5 = ""; // await fileMd5(File(ipaFilePath));
    store.createTime = DateUtil.formatLongTime(DateTime.now());
    storeList.add(store);
    storeList.sort((e1, e2) => e2.createTime.compareTo(e1.createTime));
    File("${config.storeDirectory}/store.json").writeAsStringSync(
        _prettyEncode(storeList.map((e) => e.toJson()).toList()));
    final File homePage = File(config.indexFile);
    if (!homePage.existsSync()) {
      homePage.createSync(recursive: true);
    }
    await homePage.writeAsString(_buildHomePage(config, storeList));
    final end = DateTime.now().millisecondsSinceEpoch;

    print("time: ${end - start} ms");
    return jsonEncode({"code": 200, "msg": "OK"});
  } catch (e, s) {
    print(e);
    print(s);
    return jsonEncode({"code": 999, "msg": "系统错误"});
  }
}

String _formatPath(String path) {
  return path.replaceAll("..", "").replaceAll("/", "").replaceAll("\\", "");
}

String _prettyEncode(dynamic object) {
  final dynamic prettyString = JsonEncoder.withIndent('  ').convert(object);
  return prettyString;
}

String _buildHomePage(Config config, List<Store> storeList) {
  final List<Store> list = [];
  //_storeList已经按创建时间排序，相同的bundleIdentifier只保留最新
  final Set<String> bid = Set<String>();
  for (Store store in storeList) {
    if (!bid.contains(store.bundleIdentifier)) {
      bid.add(store.bundleIdentifier);
      list.add(store);
    }
  }
  //生成静态首页
  final String body = list.map((e) => '''
  <a style="font-size: 20px;" href="itms-services://?action=download-manifest&amp;url=${config.serverUrl}/${config.packageDirectory}/${_buildUid(e.bundleIdentifier, e.bundleVersion, e.buildNumber)}.plist">${e.title}</a>&nbsp;${e.bundleIdentifier}(${e.bundleVersion}+${e.buildNumber})
  <br>
  <br>
  ''').toList().join("");

  return '''
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="robots" content="all">
  <meta name="viewport"
    content="initial-scale=1,width=device-width,height=device-height,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="format-detection" content="telephone=no, address=no">
  <title>一键安装</title>
  <script charset="utf-8" src="chrome-extension://jgphnjokjhjlcnnajmfjlacjnjkhleah/js/btype.js"></script>
  <div id="BIJIA_HAS_BUILT"></div>
</head>

<body>
  $body
</body>

</html>
  ''';
}

///生成uid
String _buildUid(
        String bundleIdentifier, String bundleVersion, String buildNumber) =>
    "${bundleIdentifier}_${bundleVersion}_${buildNumber}";

///生成plist
String _buildPlist(Config config, String bundleIdentifier, String bundleVersion,
    String buildNumber, String title, String subtitle) {
  final String uid = _buildUid(bundleIdentifier, bundleVersion, buildNumber);
  return '''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>items</key>
        <array>
            <dict>
                <key>assets</key>
                <array>
                    <dict>
                        <key>kind</key>
                        <string>software-package</string>
                        <key>url</key>
                        <string>${config.serverUrl}/${config.packageDirectory}/$uid.ipa</string>
                    </dict>
                    <dict>
                        <key>kind</key>
                        <string>display-image</string>
                        <key>needs-shine</key>
                        <true/>
                        <key>url</key>
                        <string>${config.serverUrl}/${config.packageDirectory}/$uid.png</string>
                    </dict>
                    <dict>
                        <key>kind</key>
                        <string>full-size-image</string>
                        <key>needs-shine</key>
                        <true/>
                        <key>url</key>
                        <string>${config.serverUrl}/${config.packageDirectory}/$uid.png</string>
                    </dict>
                </array>
                <key>metadata</key>
                <dict>
                    <key>bundle-identifier</key>
                    <string>$bundleIdentifier</string>
                    <key>bundle-version</key>
                    <string>$bundleVersion</string>
                    <key>kind</key>
                    <string>software</string>
                    <key>subtitle</key>
                    <string>$title</string>
                    <key>title</key>
                    <string>$subtitle</string>
                </dict>
            </dict>
        </array>
    </dict>
</plist>
  ''';
}
